/* ---------------------------------------------Marlin Toolkit----------------------------------------*
Authors : Daniel Randle, Alex Bourne
Date Created : Mar 29 2013

File:
nsengine.cpp

Description:
This file contains the definition for the NSEngine class functions along with any
helper functions associated with the class
*-----------------------------------------------------------------------------------------------------*/

#include <nsengine.h>
#include <logfile.h>



NSEngine::NSEngine(): mapArea(new NSMapArea(this)),
	mapBuilder( new NSMapBuilder(mapArea)),
	objects(new NSObjectSet(mapArea)),
	shaders(new NSShaderSet(objects)),
	camera(new NSCamera("Camera1",NSCamera::FREE,10.0f,0.1f,2.0f,NSVec3Df(-20.0f,-20.0f,-10.0f))),
	pu(false),
	showOccupiedTiles(false)
{
	LogFile("Creating Engine...");
	NSLightShader * ls = (NSLightShader*)addShader(NSShader::LIGHT,"GeometryShader","gbuffer.fsh","gbuffer.vsh");
	ls->updateUniforms();
	setDirectionalLight("DirectionalLight");

	frameBuffers.resize(NSFrameBufferObject::BufferCount);
	frameBuffers[NSFrameBufferObject::Render] = new NSFrameBufferObject(objects,mapArea);
	frameBuffers[NSFrameBufferObject::GBuffer] = new NSGBuffer(objects,mapArea);
}

NSEngine::~NSEngine()
{
	LogFile("Deleting Engine...");
	delete camera;
	delete shaders;
	delete mapArea;
	delete objects;
	while (frameBuffers.begin() != frameBuffers.end())
	{
		delete frameBuffers.back();
		frameBuffers.pop_back();
	}
	LogFile("Engine Deleted");
}

void NSEngine::addObject(NSShader* shader, NSObject *object)
{
	objects->addObject(shader,object);
}

NSObject * NSEngine::addObject(NSObject::ObjectType type, NSShader* shader, 
	const std::string & name, 
	const std::string & mfileName, 
	const std::string & mDirectory, 
	const std::string & texDir)
{
	return objects->addObject(type,shader,name,mfileName,mDirectory,texDir);
}

void NSEngine::addReference(NSObjectReference* toAdd)
{
	pu = true;
	mapArea->addReference(toAdd);
}

NSObjectReference * NSEngine::addReference(
	NSObject * baseObj, 
	const NSVec3Df & position, 
	const NSVec3Df & orientation, 
	const NSVec3Df & scale)
{
	pu = true;
	return mapArea->addReference(baseObj,position,orientation,scale,shaders);
}

NSObjectReference * NSEngine::addReference(
	const std::string & objectName, 
	const NSVec3Df & position, 
	const NSVec3Df & orientation, 
	const NSVec3Df & scale)
{
	pu = true;
	return mapArea->addReference(objectName,position,orientation,scale,shaders);
}

void NSEngine::addShader(NSShader * shader)
{
	shaders->addShader(shader);
}

NSShader * NSEngine::addShader(NSShader::ShaderProgType type, 
	const std::string & _shaderID, 
	const std::string & fragmentFileName, 
	const std::string & vertexFileName, 
	const std::string & geometryFileName,
	const std::string & shaderDirectory)
{
	return shaders->addShader(type,_shaderID,fragmentFileName,vertexFileName,geometryFileName,shaderDirectory);
}

void NSEngine::addCamera(NSCamera * cam)
{
	removeCamera();
	camera = cam;
}

NSCamera * NSEngine::addCamera( const std::string & camID, 
	NSCamera::CameraMode mode,
	float _speed,
	float sens,
	float zoomFac,
	const NSVec3Df & pos,
	const NSVec3Df & orient,
	const NSVec3Df & rotPoint,
	const NSMatrix3Df & startingOrientation)
{
	removeCamera();
	camera = new NSCamera(camID,mode,_speed,sens,zoomFac,pos,orient,rotPoint,startingOrientation);
	return camera;
}

void NSEngine::clearMap()
{
	pu = true;
	mapArea->clear();
}

NSObjectReference * NSEngine::createReference(
	NSObject * baseObj, 
	const NSVec3Df & position, 
	const NSVec3Df & orientation, 
	const NSVec3Df & scale)
{
	return mapArea->createReference(baseObj,position,orientation,scale,shaders);
}

void NSEngine::deserializeMap(std::ifstream & in)
{
	pu = true;
	mapArea->deserialize(in);
}

void NSEngine::deserializeObjects(std::ifstream & in)
{

}

void NSEngine::deserializeShaders(std::ifstream & in)
{

}

void NSEngine::draw()
{
	frameBuffers[NSFrameBufferObject::GBuffer]->draw(projMat,camera);
}

bool NSEngine::isOnScreen(const NSVec3Df & worldPos)
{
	NSVec3Df tmp = getScreenSpace(worldPos);
	return (tmp.x < 1.2f && tmp.x > -1.2f && tmp.y < 1.2f && tmp.y > -1.2f && tmp.z < 30.0f && tmp.z > 0.0f);
}

void NSEngine::drawOverlays()
{
	glDepthMask(GL_TRUE);
	glEnable(GL_DEPTH_TEST);
	glClear(GL_DEPTH_BUFFER_BIT);
	NSMatrix4Df projCamMat(projMat * camera->getTransform());

	redrawObjectsNoColorBuffer(projCamMat);
	mapArea->drawOverlays(projCamMat);

	glDepthMask(GL_FALSE);
	glDisable(GL_DEPTH_TEST);
}

NSCamera * NSEngine::getCamera()
{
	return camera;
}

NSMatrix4Df NSEngine::getCameraMatrix()
{
	return camera->getTransform();
}

NSMatrix4Df NSEngine::getInverseProjectionMatrix()
{
	return inverseProjMat;
}

NSMapBuilder * NSEngine::getMapBuilder()
{
	return mapBuilder;
}

NSMapArea::MapSize NSEngine::getMapSize()
{
	return mapArea->getMapSize();
}

NSVec2Df NSEngine::getNDCFromMousePos(const NSVec2Df & pos)
{
	NSVec2Df ret;
	ret.x = (pos.x*2 / float(mapArea->getScreenWidth())  ) - 1.0f;
	ret.y = (pos.y*2 / float(mapArea->getScreenHeight()) ) - 1.0f;
	return ret;
}

NSVec2Df NSEngine::getNDCFromMousePos(float xPos, float yPos)
{
	return getNDCFromMousePos(NSVec2Df(xPos,yPos));
}

NSObject * NSEngine::getObject(const std::string & objectName)
{
	return objects->getObject(objectName);
}

NSObject * NSEngine::getObject(unsigned int objectID)
{
	return objects->getObject(objectID);
}

NSEngine::ObjectIndex NSEngine::getObjectIndex(const NSVec2Df & mousePos)
{
	return getObjectIndex(mousePos.x, mousePos.y);
}

NSEngine::ObjectIndex NSEngine::getObjectIndex(int xMousePos, int yMousePos)
{
	yMousePos = projInfo.height - yMousePos;
	return ((NSGBuffer*)frameBuffers[NSFrameBufferObject::GBuffer])->getObjectIndex(
		xMousePos, yMousePos);
}

NSMatrix4Df NSEngine::getProjectionMatrix()
{
	return projMat;
}

NSObjectReference* NSEngine::getReference(unsigned int refID, NSObject * baseObj)
{
	return mapArea->getReference(refID, baseObj);
}

unsigned int NSEngine::getReferenceCount(NSObject * baseObj)
{
	return mapArea->getReferenceCount(baseObj);
}

NSVec3Df NSEngine::getScreenCenterWolrdPos(float depthFromCam)
{
	NSVec3Df pos = camera->getPosition(NSCamera::FREE)*-1;
	pos += camera->getUVNVec(NSCamera::NVector)*depthFromCam;
	return pos;
}

NSVec3Df NSEngine::getScreenSpace(const NSVec3Df & worldSpace)
{
	NSVec4Df pos = projMat * camera->getTransform() * NSVec4Df(worldSpace.x,worldSpace.y,worldSpace.z,1.0f);
	pos.x /= pos.w;
	pos.y /= pos.w;
	return NSVec3Df(pos.x,pos.y,pos.z);
}

NSSelectionModel * NSEngine::getSelectionModel()
{
	return mapArea->getSelectionModel();
}

NSShader * NSEngine::getShader(unsigned int progID)
{
	return shaders->getShader(progID);
}

NSShader * NSEngine::getShader(const std::string & shaderID)
{
	return shaders->getShader(shaderID);
}

bool NSEngine::getShowOccupiedTiles() const
{
	return showOccupiedTiles;
}

unsigned int NSEngine::getTotalReferenceCount()
{
	return mapArea->getTotalReferenceCount();
}

int NSEngine::height()
{
	return projInfo.height;
}

void NSEngine::redrawObjectsNoColorBuffer(const NSMatrix4Df & projCamMat)
{
	NSShader * gShader = getShader("GeometryShader");

	gShader->enable();
	gShader->setUniform("projCamMat",projCamMat);

	NSObjectSet::ObjectIterator objIter = objectsBegin(gShader);
	glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
	while (objIter != objectsEnd(gShader))
	{
		(*objIter)->draw();
		++objIter;
	}
	glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
}

bool NSEngine::removeCamera()
{
	if (camera != NULL)
	{
		delete camera;
		camera = NULL;
		return true;
	}
	return false;
}

bool NSEngine::removeObject(const std::string & objectName)
{
	return objects->removeObject(objectName);
}

bool NSEngine::removeObject(NSObject *object)
{
	return objects->removeObject(object);
}

bool NSEngine::removeObjectsShader(NSShader * toRemove)
{
	return objects->removeObjectsShader(toRemove);
}

bool NSEngine::removeAllReferencesOf(NSObject * base)
{
	pu = true;
	return mapArea->removeAllReferencesOf(base);
}

bool NSEngine::removeReference(unsigned int referenceID, NSObject * baseObj)
{
	pu = true;
	return mapArea->removeReference(referenceID, baseObj);
}

bool NSEngine::removeReference(NSObjectReference* reference)
{
	pu = true;
	return mapArea->removeReference(reference);
}

bool NSEngine::removeShader(NSShader * shader)
{
	return shaders->removeShader(shader);
}

bool NSEngine::removeShader(unsigned int progID)
{
	return shaders->removeShader(progID);
}

bool NSEngine::removeShader(const std::string & shaderID)
{
	return shaders->removeShader(shaderID);
}

void NSEngine::resize(int width, int height)
{
	setProjectionMatrix(projInfo.FOVAngle,width,height,projInfo.zNear,projInfo.zFar);
	//resizeBuffer(Picking, width, height);
	resizeBuffer(NSFrameBufferObject::GBuffer,width,height);
	resizeBuffer(NSFrameBufferObject::Render, width, height);
	mapArea->resizeScreen(width,height);
}

void NSEngine::resizeBuffer(NSFrameBufferObject::BufferType which, int width, int height)
{
	frameBuffers[which]->resetSize(width,height);
}

void NSEngine::serializeMap(std::ofstream & out)
{
	mapArea->serialize(out);
}

void NSEngine::serializeObjects(std::ofstream & out)
{

}

void NSEngine::serializeShaders(std::ofstream & out)
{

}

void NSEngine::setAllShaderUniforms(const char * variableName, const NSMatrix4Df & data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, const NSMatrix3Df & data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, const NSVec4Df & data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, const NSVec3Df & data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, const NSVec2Df & data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, float data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, int data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setAllShaderUniforms(const char * variableName, unsigned int data) const
{
	shaders->setAllUniforms(variableName,data);
}

void NSEngine::setDirectionalLight(NSDirectionLight * dLight)
{
	mapArea->setDirectionalLight(dLight);
}

void NSEngine::setDirectionalLight(
	const std::string & name, 
	const NSVec3Df & _direction, 
	float diffuse, 
	float ambient,
	float specularPower,
	float specularIntensity,
	const NSVec3Df & _color)
{
	NSDirectionLight * dLight = new NSDirectionLight(name,"","","",_direction,diffuse,ambient,specularPower, specularIntensity, _color);
	dLight->init();
	setDirectionalLight(dLight);
}

void NSEngine::setProjectionMatrix(float FOVAngle, int width, int height, float zNear, float zFar)
{
	projInfo.FOVAngle = FOVAngle;
	projInfo.width = width;
	projInfo.height = height;
	projInfo.zNear = zNear;
	projInfo.zFar = zFar;

	projMat = NSMatrix4Df::getProjectionMatrix(FOVAngle,width,height,zNear,zFar);

	mapArea->setProjectionMatrix(projMat);
	inverseProjMat = projMat.getInverse();
}

void NSEngine::update()
{
	camera->update();
	if (pu)
	{
		mapArea->updateAll();
		pu = false;
	}
	mapBuilder->update();
}

int NSEngine::width()
{
	return projInfo.width;
}

NSMapArea::Iterator NSEngine::mapBegin()
{
	return mapArea->begin();
}

NSMapArea::Iterator NSEngine::mapEnd()
{
	return mapArea->end();
}

NSObjectSet::Iterator NSEngine::objectsMapBegin()
{
	return objects->begin();
}

NSObjectSet::Iterator NSEngine::objectsMapEnd()
{
	return objects->end();
}

NSObjectSet::ObjectIterator NSEngine::objectsBegin( NSShader *shader )
{
	return objects->objectsBegin(shader);
}

NSObjectSet::ObjectIterator NSEngine::objectsEnd( NSShader * shader )
{
	return objects->objectsEnd(shader);
}

NSMapArea::ObjectIterator NSEngine::objectsRefSetBegin(NSObject::ObjectType whichType)
{
	return mapArea->objectsBegin(whichType);
}

NSMapArea::ObjectIterator NSEngine::objectsRefSetEnd(NSObject::ObjectType whichType)
{
	return mapArea->objectsEnd(whichType);
}

NSMapArea::RefIterator NSEngine::refsBegin(NSObject * baseObj)
{
	return mapArea->refsBegin(baseObj);
}

NSMapArea::RefIterator NSEngine::refsEnd(NSObject * baseObj)
{
	return mapArea->refsEnd(baseObj);
}

void NSEngine::setShowOccupiedTiles(bool show)
{
	showOccupiedTiles = show;
}

NSShaderSet::Iterator NSEngine::shadersBegin()
{
	return shaders->begin();
}

NSShaderSet::Iterator NSEngine::shadersEnd()
{
	return shaders->end();
}


NSEngine::ProjectionInfo::ProjectionInfo(): FOVAngle(0.0f),
	width(0),
	height(0),
	zNear(0.0f),
	zFar(0.0f)
{}